#include "RoomManager.h"
#include "GameObjects/Room.h"
#include "GameObjects/Exit.h"
#include "GameObjects/Item.h"

#include "ItemManager.h"

#include "StringHelper.h"

#include <iostream>
#include <filesystem>
#include <regex>
#include <algorithm>

RoomManagerPtr RoomManager::singleton;

RoomManagerPtr RoomManager::getSingleton()
{
	if(RoomManager::singleton == nullptr)
		RoomManager::singleton = std::make_shared<RoomManager>();
	return RoomManager::singleton;
}

RoomManager::RoomManager()
{
}

RoomManager::~RoomManager()
{
}

void RoomManager::init(const std::string &path)
{
	std::string room_title;

	std::cout << "Loading rooms" << std::endl;
	if(std::tr2::sys::is_directory(std::tr2::sys::path(path)))
	{
		for(std::tr2::sys::directory_iterator itr(path); itr!=std::tr2::sys::directory_iterator(); ++itr)
		{
			auto filename = itr->path().filename();
			std::cout << "- " << filename << ": ";
			std::ifstream file(path+"\\"+filename);
			if(file.is_open())
			{
				std::vector<std::string> lines;
				while(file.good())
				{
					std::string line;
					std::getline(file, line);
					if(line.empty()) // Seperation line
						continue;
					if(line[0] == '/' && line[1] == '/') //Comment
						continue;
					lines.push_back(line);
				}
				file.close();

				//Parse title and description (First two lines)
				room_title = lines[0];
				std::cout << room_title << std::endl;

				auto temp_data = TempRoomDataPtr(new TempRoomData);

				//Loop over the rest of the lines and parse the text
				unsigned int parse_index = 1;
				while(parse_index < lines.size())
				{
					//DESCRIPTION
					if(StringHelper::toUpper(lines[parse_index], lines[parse_index].size()).find("DESCRIPTION") != std::string::npos)
					{
						parse_index++;
						parseDescription(lines, parse_index, temp_data->description);
					}
					//EXITS
					else if(StringHelper::toUpper(lines[parse_index], lines[parse_index].size()).find("EXITS") != std::string::npos)
					{
						parse_index++;
						parseExits(lines, parse_index, temp_data);
					}
					//SETTINGS
					else if(StringHelper::toUpper(lines[parse_index], lines[parse_index].size()).find("SETTINGS") != std::string::npos)
					{
						parse_index++;
						parseSettings(lines, parse_index, temp_data->settings);
					}
					//ITEMS
					else if(StringHelper::toUpper(lines[parse_index], lines[parse_index].size()).find("ITEMS") != std::string::npos)
					{
						parse_index++;
						parseItems(lines, parse_index, temp_data);
					}
					//MONSTERS
					//SPECIALS

					//KEEP LOOP ALIVE
					else
					{
						parse_index++;
					}
				}

				
				auto room = std::make_shared<Room>(room_title, temp_data->description);
				unsigned int room_id = (unsigned int)atoi((filename.substr(0, filename.find_last_of("."))).c_str());
				rooms[room_id] = room;
				temp_room_data[room_id] = std::move(temp_data);
			}
			else
			{
				std::cout << "Unable to open file!" << std::endl;
			}
		}
	}
	else 
	{
		std::cout << (std::tr2::sys::exists(std::tr2::sys::path(path)) ? "Found: " : "Not found: ") << path << '\n';
	}

	//Apply parsed data and settings
	for(auto it = rooms.begin(); it != rooms.end(); ++it)
	{
		linkExits(it);
		applySettings(it);
        spawnItems(it);
	}

	std::cout << "done." << std::endl;
}

///////////////////////////////////
// DESCRIPTION PARSING
///////////////////////////////////
void RoomManager::parseDescription(const std::vector<std::string> &lines, unsigned int &index, std::string &description)
{
	while(index < lines.size())
	{
		//Ignore start bracket
		if(lines[index].find("{") != std::string::npos)
		{
			index++;
			continue;
		}

		//Finish parsing at end bracket
		if(lines[index].find("}") != std::string::npos)
		{
			index++;
			description.erase(std::remove(begin(description), end(description), '\t'), end(description));
			return;
		}

		std::string line = lines[index++];
		
		description += " " + line;
	}

	std::cout << "ERROR: OUT OF BOUNDS WHEN PARSING ROOM DESCRIPTION FOR " << lines[0] << "!" << std::endl;
}

///////////////////////////////////
// EXIT PARSING
///////////////////////////////////
void RoomManager::parseExits(const std::vector<std::string> &lines, unsigned int &index, TempRoomDataPtr &data)
{
	while(index < lines.size())
	{
		//Ignore start bracket
		if(lines[index].find("{") != std::string::npos)
		{
			index++;
			continue;
		}

		//Finish parsing at end bracket
		if(lines[index].find("}") != std::string::npos)
		{
			index++;
			return;
		}

		//Otherwise, we parse the line as an exit
		parseExitLine(lines, index, data); 
	}

	std::cout << "ERROR: OUT OF BOUNDS WHEN PARSING ROOM EXITS FOR " << lines[0] << "!" << std::endl;
}

void RoomManager::parseExitLine(const std::vector<std::string> &lines, unsigned int &index, TempRoomDataPtr &data)
{
	const std::string &line = lines[index++];

	std::string exit_dir;
	unsigned int exit_id = 0;
	parseExitHeader(line, exit_dir, exit_id);

	if(exit_dir.find("WEST") != std::string::npos)
	{
		data->w.direction = exit_dir;
		data->w.room_id =	exit_id;
		parseExitContent(lines, index, data->w);
	}
	else if(exit_dir.find("NORTH") != std::string::npos)
	{
		data->n.direction = exit_dir;
		data->n.room_id =	exit_id;
		parseExitContent(lines, index, data->n);
	}
	else if(exit_dir.find("EAST") != std::string::npos)
	{
		data->e.direction = exit_dir;
		data->e.room_id =	exit_id;
		parseExitContent(lines, index, data->e);
	}
	else if(exit_dir.find("SOUTH") != std::string::npos)
	{
		data->s.direction = exit_dir;
		data->s.room_id =	exit_id;
		parseExitContent(lines, index, data->s);
	}
	else if(exit_dir.find("UP") != std::string::npos)
	{
		data->u.direction = exit_dir;
		data->u.room_id =	exit_id;
		parseExitContent(lines, index, data->u);
	}
	else if(exit_dir.find("DOWN") != std::string::npos)
	{
		data->d.direction = exit_dir;
		data->d.room_id =	exit_id;
		parseExitContent(lines, index, data->d);
	}
	else
	{
		std::cout << "ERROR: FAILED TO PARSE EXIT HEADER!" << std::endl;
	}
}

void RoomManager::parseExitHeader(const std::string &line, std::string &direction, unsigned int &id)
{
	auto pos = line.find(" ");
	direction = line.substr(0, pos);
	auto trimmed_start = direction.find_first_not_of("\t");
	auto trimmed_end = direction.find_last_not_of("\t") - trimmed_start + 1;
	direction = direction.substr(trimmed_start, trimmed_end); //Trim away any leading or ending tabs

	std::string exit_id_string = line.substr(pos+1, line.size());
	trimmed_start = exit_id_string.find_first_not_of("\t");
	trimmed_end = exit_id_string.find_last_not_of("\t") - trimmed_start + 1;
	exit_id_string = exit_id_string.substr(trimmed_start, trimmed_end); //Trim away any leading or ending tabs
	id = atoi(exit_id_string.c_str());

	/*std::regex reg_dir("([[:alpha:]]*)+[[:space:]]+([[:digit:]]*)", std::regex::extended);
	std::regex reg_id("[[:space:]]+([[:digit:]]*)", std::regex::extended);
	std::match_results<std::string::const_iterator> reg_res;

	if(std::regex_search(line, reg_res, reg_dir))
	{
		direction = reg_res[0];
		std::string r = reg_res[2];
		direction = StringHelper::toUpper(direction, direction.size());
	}
	else
	{
		std::cout << "ERROR: FAILED TO PARSE THE EXIT ID FOR EAST!" << std::endl;
		return;
	}

	if(std::regex_search(line, reg_res, reg_id))
	{
		std::string exit_id_string = reg_res[0];
		id = atoi(exit_id_string.c_str());
	}
	else
	{
		std::cout << "ERROR: FAILED TO PARSE THE EXIT ID FOR EAST!" << std::endl;
		return;
	}*/
}

void RoomManager::parseExitContent(const std::vector<std::string> &lines, unsigned int &index, TempExitData &data)
{
	while(index < lines.size())
	{
		//Ignore start bracket
		if(lines[index].find("{") != std::string::npos)
		{
			index++;
			continue;
		}

		//Finish parsing at end bracket
		if(lines[index].find("}") != std::string::npos)
		{
			index++;
			return;
		}

		//DESCRIPTION
		if(StringHelper::toUpper(lines[index], lines[index].size()).find("DESCRIPTION") != std::string::npos)
		{
			index++;
			parseDescription(lines, index, data.description);
		}

		//MONSTERS

		//ITEMS

		//SPECIALS

		//SETTINGS
		else if(StringHelper::toUpper(lines[index], lines[index].size()).find("SETTINGS") != std::string::npos)
		{
			index++;
			parseSettings(lines, index, data.settings);
		}
		//KEEP LOOP ALIVE
		else
		{
			index++;
		}
	}

	std::cout << "ERROR: OUT OF BOUNDS WHEN PARSING ROOM SETTINGS FOR " << lines[0] << "!" << std::endl;
}

void RoomManager::linkExits(std::unordered_map<unsigned int, RoomPtr>::iterator &it)
{
	auto temp_it = temp_room_data.find(it->first);
	if(temp_it == temp_room_data.end())
		return;

	if(temp_it->second->w.room_id > 0)
	{
		auto room_at_exit_it = rooms.find(temp_it->second->w.room_id);
		auto exit = it->second->add(std::make_shared<Exit>(Exit::WEST, room_at_exit_it->second, temp_it->second->w.description));
		for(unsigned int i = 0; i < temp_it->second->w.settings.size(); i++)
			exit->add(static_cast<Exit::Setting>(temp_it->second->w.settings[i]));
	}
	if(temp_it->second->n.room_id > 0)
	{
		auto room_at_exit_it = rooms.find(temp_it->second->n.room_id);
		auto exit = it->second->add(std::make_shared<Exit>(Exit::NORTH, room_at_exit_it->second, temp_it->second->n.description));
		for(unsigned int i = 0; i < temp_it->second->n.settings.size(); i++)
			exit->add(static_cast<Exit::Setting>(temp_it->second->n.settings[i]));
	}
	if(temp_it->second->e.room_id > 0)
	{
		auto room_at_exit_it = rooms.find(temp_it->second->e.room_id);
		auto exit = it->second->add(std::make_shared<Exit>(Exit::EAST, room_at_exit_it->second, temp_it->second->e.description));
		for(unsigned int i = 0; i < temp_it->second->e.settings.size(); i++)
			exit->add(static_cast<Exit::Setting>(temp_it->second->e.settings[i]));
	}
	if(temp_it->second->s.room_id > 0)
	{
		auto room_at_exit_it = rooms.find(temp_it->second->s.room_id);
		auto exit = it->second->add(std::make_shared<Exit>(Exit::SOUTH, room_at_exit_it->second, temp_it->second->s.description));
		for(unsigned int i = 0; i < temp_it->second->s.settings.size(); i++)
			exit->add(static_cast<Exit::Setting>(temp_it->second->s.settings[i]));
	}
	if(temp_it->second->u.room_id > 0)
	{
		auto room_at_exit_it = rooms.find(temp_it->second->u.room_id);
		auto exit = it->second->add(std::make_shared<Exit>(Exit::UP, room_at_exit_it->second, temp_it->second->u.description));
		for(unsigned int i = 0; i < temp_it->second->u.settings.size(); i++)
			exit->add(static_cast<Exit::Setting>(temp_it->second->u.settings[i]));
	}
	if(temp_it->second->d.room_id > 0)
	{
		auto room_at_exit_it = rooms.find(temp_it->second->d.room_id);
		auto exit = it->second->add(std::make_shared<Exit>(Exit::DOWN, room_at_exit_it->second, temp_it->second->d.description));
		for(unsigned int i = 0; i < temp_it->second->d.settings.size(); i++)
			exit->add(static_cast<Exit::Setting>(temp_it->second->d.settings[i]));
	}
}

///////////////////////////////////
// SETTINGS PARSING
///////////////////////////////////
void RoomManager::parseSettings(const std::vector<std::string> &lines, unsigned int &index, std::vector<unsigned int> &settings)
{
	while(index < lines.size())
	{
		//Ignore start bracket
		if(lines[index].find("{") != std::string::npos)
		{
			index++;
			continue;
		}

		//Finish parsing at end bracket
		if(lines[index].find("}") != std::string::npos)
		{
			index++;
			return;
		}

		//Otherwise, we parse the line as a setting
		const std::string &setting = lines[index++];

		//Check for ROOM settings
		if(StringHelper::toUpper(setting, setting.size()).find("SPAWN") != std::string::npos)
			settings.push_back(Room::SPAWN);

		//Check for EXIT settings
		else if(StringHelper::toUpper(setting, setting.size()).find("SLIPPERY_DOWN") != std::string::npos)
			settings.push_back(Exit::SLIPPERY_DOWN);
		else if(StringHelper::toUpper(setting, setting.size()).find("SLIPPERY_UP") != std::string::npos)
			settings.push_back(Exit::SLIPPERY_UP);
	}

	std::cout << "ERROR: OUT OF BOUNDS WHEN PARSING ROOM SETTINGS FOR " << lines[0] << "!" << std::endl;
}

void RoomManager::applySettings(std::unordered_map<unsigned int, RoomPtr>::iterator &it)
{
	auto temp_it = temp_room_data.find(it->first);
	if(temp_it == temp_room_data.end())
		return;

	for(unsigned int i = 0; i < temp_it->second->settings.size(); i++)
	{
		it->second->add(static_cast<Room::Setting>(temp_it->second->settings[i]));
		if(temp_it->second->settings[i] == Room::SPAWN)
			spawn_room = it->second;
	}
}

///////////////////////////////////
// ITEM PARSING
///////////////////////////////////
void RoomManager::parseItems(const std::vector<std::string> &lines, unsigned int &index, TempRoomDataPtr &data)
{
	while(index < lines.size())
	{
		//Ignore start bracket
		if(lines[index].find("{") != std::string::npos)
		{
			index++;
			continue;
		}

		//Finish parsing at end bracket
		if(lines[index].find("}") != std::string::npos)
		{
			index++;
			return;
		}

		//Otherwise, we parse the line as an item
		std::string item_id_string = lines[index++];
		auto end = item_id_string.find_last_not_of(" ") + 1;
		item_id_string = item_id_string.substr(0, end); //Trim out any whitespace and comments at the end
		auto item_id = atoi(item_id_string.c_str());
		data->item_ids.push_back(item_id);
	}

	std::cout << "ERROR: OUT OF BOUNDS WHEN PARSING ROOM EXITS FOR " << lines[0] << "!" << std::endl;
}

void RoomManager::spawnItems(std::unordered_map<unsigned int, RoomPtr>::iterator &it)
{
	auto temp_it = temp_room_data.find(it->first);
	if(temp_it == temp_room_data.end())
		return;

    for(unsigned int i = 0; i < temp_it->second->item_ids.size(); i++)
	{
        auto item = ItemManager::getSingleton()->get(temp_it->second->item_ids[i]);
        if(item)
            it->second->add(std::static_pointer_cast<GameObject>(item));
	}
}
